package com.whately.core.base.keygen;

import com.whately.core.base.utils.IntegerUtils;
import lombok.Data;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 带时间戳的序列
 */
@Data
public class StampedSequence {
    private long ts; // 时间戳
    private int sequence; // 序列

    private StampedSequence(){
    }

    public StampedSequence(long ts, int sequence) {
        this.ts = ts;
        this.sequence = sequence;
    }

    public static AtomicStampedReference<StampedSequence> initRefer(){
        StampedSequence dt = new StampedSequence(0, StampedSequence.initRandomStarter());
        return new AtomicStampedReference<StampedSequence>(dt, 0);
    }

    public static StampedSequence nextSequence(AtomicStampedReference<StampedSequence> current, long MAX_SEQUENCE){
        return nextSequence(current,MAX_SEQUENCE,TimeUnit.MILLISECONDS);
    }

    public static StampedSequence nextSequenceTimeInSecond(AtomicStampedReference<StampedSequence> current, long MAX_SEQUENCE){
        return nextSequence(current,MAX_SEQUENCE,TimeUnit.SECONDS);
    }


    private static StampedSequence nextSequence(AtomicStampedReference<StampedSequence> current, long MAX_SEQUENCE, TimeUnit timeUnit) {
        //下一个时间戳序列
        StampedSequence nextSequence = new StampedSequence();

        //版本
        int[] versionHolder = new int[1];
        while (true) {
            long now = getCurrentTs(timeUnit);
            StampedSequence curSequence = current.get(versionHolder);
            if (now < curSequence.getTs()) {
                throw new RuntimeException("Clock moved backwards!");
            }else if (curSequence.getTs() == now) {
                if (curSequence.getSequence() >= MAX_SEQUENCE) {
                    //满序列等待下一毫秒
                    continue;
                }
                nextSequence.setTs(curSequence.getTs());
                nextSequence.setSequence( curSequence.getSequence() + 1);
                boolean set = current.compareAndSet(curSequence, nextSequence, versionHolder[0], versionHolder[0] + 1);
                if (!set) {
                    // 无锁更新失败，重新获取
                    continue;
                }
                break;
            } else {
                nextSequence.setTs(now);
                nextSequence.setSequence(initRandomStarter());
                boolean set = current.compareAndSet(curSequence, nextSequence, versionHolder[0], versionHolder[0] + 1);
                if (!set) {
                    // 无锁更新失败，重新获取
                    continue;
                }
                break;
            }
        }
        return nextSequence;
    }

    /**
     * 当前时间
     * @return
     */
    private static long getCurrentTs(TimeUnit timeUnit){
        if(TimeUnit.SECONDS.equals(timeUnit)){
            return LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        }else{
            return LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        }
    }

    public static int initRandomStarter() {
        return IntegerUtils.random(0,9);

        //return 0;
    }

}
